/******************************************************************************
 *      Mestrado em Ciência da Computação - UFMS
 *		Redes de Computadores - Prof. Dr. Ronaldo Alves Ferreira
 *		Alunos: Geraldo Landre, Marcel Tolentino, Vinícius Bueno
 *****************************************************************************/
#include "my_socket.h"
#include "tcptimer.h"

int my_socket(int domain, int type, int protocol)
{
    if(domain != PF_INET || type != SOCK_STREAM || protocol != 0)
        return SYSERR; // NOT IMPLEMENTED
    
    struct tcb* ptcb = tcballoc();
    return ptcb->tcb_dvnum;
    
    //return socket(domain, type, protocol);
}

int my_bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
    if(addr == NULL || addrlen != sizeof(struct sockaddr_in))
        return SYSERR;
    if(sockfd <= 0 || sockfd > NTCP)
        return SYSERR;
    struct sockaddr_in *my_addr = (struct sockaddr_in *)addr;
    if(my_addr->sin_family != AF_INET)
        return SYSERR; // NOT IMPLEMENTED.

    //if(ntohs(my_addr->sin_port) != localport) {
    if(my_addr->sin_port != localport) {
        printf("porta errada: %d (localport: %d)\n", my_addr->sin_port, localport);
        return SYSERR;
    }
    
    struct tcb* ptcb = tcbtab[sockfd];

    
    u_short lport = my_addr->sin_port;
    // TODO: endereco ip?

    int r = tcpserver(ptcb, lport);

    if(r == sockfd)
        return 0;
    return SYSERR;
}

int my_listen(int sockfd, int backlog)
{
    if(sockfd <= 0 || sockfd > NTCP || backlog <= 0)
        return SYSERR;

    struct tcb* ptcb = tcbtab[sockfd];

    if(ptcb->tcb_lport == 0)
        return SYSERR;

    int r = tcplq(ptcb, backlog);
    if(r == OK)
        return 0;
    return SYSERR;
}

int my_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
{
    if(*addrlen != sizeof(struct sockaddr_in)) {
        printf("addr: %d\nsizeof: %d", (int)addr, *addrlen);
        return SYSERR;
    }
    if(sockfd <= 0 || sockfd > NTCP) {
        printf("sockfd: %d\n", sockfd);
        return SYSERR;
    }
    addr = (struct sockaddr*)malloc(sizeof(struct sockaddr));
    struct sockaddr_in *my_addr = (struct sockaddr_in *)addr;

    struct tcb* ptcb = tcbtab[sockfd];
    
    if (ptcb->tcb_type != TCPT_SERVER) {
        printf("tcp type: %d\n", ptcb->tcb_type);
        return SYSERR;
    }
    
    int ret = port_recvmsg(ptcb->tcb_listenq);
    printf("nova tcb: %d\n", ret);
    my_addr->sin_family = AF_INET;
    my_addr->sin_addr.s_addr = ptcb->tcb_rip;
    printf("tcb_rport: %d\n", ptcb->tcb_rport);
    my_addr->sin_port = htons(ptcb->tcb_rport);
    printf("sin_port: %d\n", my_addr->sin_port);

    bzero(&(my_addr->sin_zero), sizeof(my_addr->sin_zero));
    
    return ret;

    //return accept(sockfd, addr, addrlen);
}

ssize_t my_recv(int fd, void *buf, size_t n, int flags)
{
    if(flags != 0)      // compatibilidade com a funcao read.
        return SYSERR;
    
    return tcpread(fd, buf, n);
}

ssize_t my_send(int fd, const void *buf, size_t n, int flags)
{
    if(flags != 0)      // compatibilidade com a funcao read.
        return SYSERR;

    return tcpwrite(fd, (unsigned char*)buf, n);
}

int my_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
    if(sockfd <= 0 || sockfd > NTCP) {
        printf("sockfd: %d\n", sockfd);
        return SYSERR;
    }

    struct sockaddr_in *my_addr = (struct sockaddr_in *)addr;
    u_short fport = my_addr->sin_port;
    ipaddr fip = my_addr->sin_addr.s_addr;
    
    struct	tcb	*ptcb;
    int		error;

    ptcb = (struct tcb *)tcballoc();

    if (ptcb == NULL) {
        printf("ptcb eh null\n");
        return SYSERR;
    }
    ptcb->tcb_error = 0;
    

    if (tcpbind(ptcb, fip, fport) != OK || tcpsync(ptcb) != OK) {
        ptcb->tcb_state = TCPS_FREE;
        sem_destroy(&(ptcb->tcb_mutex) );
        printf("erro na porra do bind\n");
        return SYSERR;
    }
    if ( (error = tcpcon(ptcb)) ){
        printf("erro na porra do connect\n");
        return error;
    }
    return ptcb->tcb_dvnum;
    // TODO
    //return connect(sockfd, addr, addrlen);
}

int my_close(int sockfd)
{
    // TODO
    return close(sockfd);
}

void *input_function()
{
    tcpinp();
    return 0;
}

void *output_function()
{
    tcpout();
    return 0;
}

void *timer_function()
{
    tcptimer();
    return 0;
}

void *udprecver_function(void *param)
{
    u_short *my_port = (u_short*)thread_getparam((thread_param*)param);
    u_char* buff = (u_char*)malloc(TCPPKTLEN);
    int ret_len;
    for(;;)
    {
        ret_len = udprecv(INADDR_ANY, *my_port, (void*)buff, TCPPKTLEN);
        printf("[udprecver_function] recieved: %d\n", ret_len);
        tcp_in((struct udp*)buff);
    }
}

void init_tcp(u_short my_port, int length) {
    printf("Starting TCP/UDP Service, port: %d\n", my_port);

    init_port_buff();
    init_thread_buff(TCP_NTHREADS + length);
    
    // inicializando por 1 para nao dar conflito
    // com a implementacao da fila, que considera
    // 0 = NULL

    tcbtab = (struct tcb **) malloc( (NTCP+1) * sizeof(struct tcb*));
    tcbqueue = sync_newqueue(NTCP);
    //printf("antes do for...\n");
    sem_init(&tcps_tmutex, 0, 0);
    int i;
    for(i = 1; i <= NTCP; i++)
    {
        tcbtab[i] = (struct tcb *) malloc(sizeof(struct tcb));
        tcbtab[i]->tcb_state = TCPS_FREE;
        tcbtab[i]->tcb_dvnum = i;
        // garante que na inicializacao a tcb nao esta associada com porta alguma:
        tcbtab[i]->tcb_lport = 0;
        // garante que a complexidade da tcballoc e tcbdealloc em O(1)
        sync_enqueue(tcbqueue, i);
    }
    //printf("depois do form...\n");
    tcps_lqsize = 5;

    TcpInSegs  = 0;
    TcpInErrs  = 0;
    TcpOutSegs = 0;
    TcpOutRsts = 0;
    TcpPassiveOpens = 0;
    TcpActiveOpens = 0;
    TcpEstabResets = 0;
    TcpCurrEstab = 0;
    TcpRetransSegs = 0;
    TcpAttemptFails = 0;

    // inicializa os parametros do udp
    init_udp(my_port);
    //printf("depois do init_udp\n");
    
    //inicializar threads de input, output e timer
    input_thread = newthread_kernel();
    output_thread = newthread_kernel();
    timer_thread = newthread_kernel();
    udp_thread = newthread_kernel();

    thread_setfunction(input_thread, input_function, NULL);
    thread_setfunction(output_thread, output_function, NULL);
    thread_setfunction(timer_thread, timer_function, NULL);
    thread_setfunction(udp_thread, udprecver_function, &my_port);

    thread_start(input_thread);
    thread_start(output_thread);
    thread_start(timer_thread);
    thread_start(udp_thread);

    //printf("antes do sem_post\n");
    sem_post(&tcps_tmutex);
    //printf("depois do sem_post\n");
}
